/**
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#ifndef PANDA_RUNTIME_ECMASCRIPT_RUNTIME_INTERFACE_H
#define PANDA_RUNTIME_ECMASCRIPT_RUNTIME_INTERFACE_H

#include "runtime/compiler.h"
#include "plugins/ecmascript/runtime/base/builtins_base.h"
#include "plugins/ecmascript/runtime/js_method.h"
#include "plugins/ecmascript/runtime/js_object.h"
#include "plugins/ecmascript/runtime/js_tagged_value.h"
#include "plugins/ecmascript/runtime/js_thread.h"
#include "plugins/ecmascript/runtime/ecma_call_params.h"
#include "plugins/ecmascript/runtime/ecma_profiling.h"
#include "runtime/mem/refstorage/reference.h"

namespace ark::ecmascript {
class EcmaVM;
class ProfileTypeInfo;
class GlobalDictionary;

enum class ObjectFieldType : uint8_t { INVALID = 0, ELEMENTS, COUNT };

class EcmaRuntimeInterface : public PandaRuntimeInterface {
public:
    explicit EcmaRuntimeInterface(const EcmaVM *ecmaVm, mem::InternalAllocatorPtr internalAllocator);
    ~EcmaRuntimeInterface() override;
    NO_COPY_SEMANTIC(EcmaRuntimeInterface);
    NO_MOVE_SEMANTIC(EcmaRuntimeInterface);

    static ecmascript::JSMethod *JsMethodCast(RuntimeInterface::MethodPtr method)
    {
        return static_cast<ecmascript::JSMethod *>(method);
    }

    size_t GetLanguageExtensionSize(Arch arch) const override;

    std::string GetMethodFullName(MethodPtr method, [[maybe_unused]] bool withSignature) const override;

    uint64_t DynamicCastDoubleToInt(double value, size_t bits) const override;

    uint8_t GetDynamicNumFixedArgs() const override;

    uint32_t GetFunctionTargetOffset(Arch arch) const override;

    uintptr_t GetGlobalVarAddress(MethodPtr method, size_t id) override;

    uint32_t GetTlsGlobalObjectOffset(Arch arch) const override
    {
        return ark::cross_values::GetJsthreadGlobalObjectOffset(arch);
    }

    size_t GetPropertyBoxOffset(Arch arch) const override
    {
        return ark::cross_values::GetJspropertyBoxValueOffset(arch);
    }

    size_t GetElementsOffset(Arch arch) const override
    {
        return ark::cross_values::GetJsobjectElementsOffset(arch);
    }

    size_t GetPropertiesOffset(Arch arch) const override
    {
        return ark::cross_values::GetJsobjectPropertiesOffset(arch);
    }

    size_t GetHClassOffset(Arch arch) const override
    {
        return -ark::cross_values::GetJshclassHclassOffset(arch) + ark::cross_values::GetJshclassBitfieldOffset(arch);
    }

    size_t GetHClassBitfieldTypeStartBit(Arch arch) const override
    {
        return ark::cross_values::GetJshclassBitfieldTypeStartBit(arch);
    }

    uint64_t GetHClassBitfieldTypeMask(Arch arch) const override
    {
        return static_cast<size_t>(ark::cross_values::GetJshclassBitfieldTypeMask(arch))
               << static_cast<size_t>(ark::cross_values::GetJshclassBitfieldTypeStartBit(arch));
    }

    uint64_t GetJshclassBitfieldClassConstructorStartBit(Arch arch) const override
    {
        return static_cast<size_t>(ark::cross_values::GetJshclassBitfieldClassConstructorStartBit(arch))
               << static_cast<size_t>(ark::cross_values::GetJshclassBitfieldTypeStartBit(arch));
    }

    size_t GetJstypeJsFunction(Arch arch) const override
    {
        return static_cast<size_t>(ark::cross_values::GetJstypeJsFunction(arch))
               << static_cast<size_t>(ark::cross_values::GetJshclassBitfieldTypeStartBit(arch));
    }

    size_t GetPrototypeHolderOffset(Arch arch) const override
    {
        return cross_values::GetJsprototypeHandlerHolderOffset(arch);
    }

    size_t GetPrototypeCellOffset(Arch arch) const override
    {
        return cross_values::GetJsprototypeHandlerProtoCellOffset(arch);
    }

    size_t GetIsChangeFieldOffset(Arch arch) const override
    {
        return ark::cross_values::GetJsprotoChangeMarkerHasChangedOffset(arch);
    }

    size_t GetDynArrayLenthOffset(Arch arch) const override
    {
        return ark::cross_values::GetJsarrayLengthOffset(arch);
    }

    EntrypointId GetGlobalVarEntrypointId() override
    {
        return EntrypointId::GET_GLOBAL_VAR_ADDRESS;
    }

    size_t ResolveInlinableNativeMethod(MethodPtr method) const override;

    bool IsInlinableNativeMethod(MethodPtr method) const override
    {
        return ResolveInlinableNativeMethod(method) != EcmaRuntimeCallerId::RUNTIME_CALLER_NUMBER;
    }

    bool IsMethodCanBeInlined([[maybe_unused]] MethodPtr method) const override
    {
        return !JsMethodCast(method)->IsNative();
    }

    size_t GetMethodCodeSize(MethodPtr method) const override
    {
        return JsMethodCast(method)->GetCodeSize();
    }

    std::string GetClassName([[maybe_unused]] ClassPtr klass) const override
    {
        if (static_cast<ark::BaseClass *>(klass)->IsDynamicClass()) {
            std::string str = std::to_string(reinterpret_cast<uintptr_t>(klass));

            return "JSHClass " + str;
        }
        ScopedMutatorLock lock;
        return ClassCast(klass)->GetName();
    }

    MethodProfile GetMethodProfile(MethodPtr method, bool fromVector) const override;

    BytecodeProfile GetBytecodeProfile(MethodProfile prof, const uint8_t *bcInst, size_t pc) const override;

    bool CanInlineLdStObjByIndex(const BytecodeInstruction *bcInst, size_t pc,
                                 MethodProfile methodProfile) const override;

    profiling::CallKind GetCallProfile(MethodPtr method, uint32_t pc, ArenaVector<uintptr_t> *methods,
                                       bool isAot) override;

    Expected<bool, const char *> AddProfile(std::string_view fname) override;

    std::string GetMethodName([[maybe_unused]] MethodPtr method) const override
    {
        return utf::Mutf8AsCString(JsMethodCast(method)->GetName().data);
    }

    MethodPtr GetMethodByIdAndSaveJsFunction(MethodPtr parentMethod, MethodId id) override;

    MethodPtr GetMethodFromFunction(uintptr_t function) const override
    {
        ScopedMutatorLock lock;
        auto *jsFunc = *(reinterpret_cast<ECMAObject **>(function));
        return jsFunc == nullptr ? nullptr : jsFunc->GetCallTarget();
    }
    compiler::AnyBaseType GetProfilingAnyType(RuntimeInterface::BytecodeProfile profile,
                                              const BytecodeInstruction *bcInst, unsigned index,
                                              profiling::AnyInputType *allowedInputType, bool *isTypeProfiled) override;

    compiler::AnyBaseType ResolveSpecialAnyTypeByConstant(coretypes::TaggedValue anyConst) override;
    size_t GetGlobalConstStringOffsetForAnyType(compiler::AnyBaseType type, Arch arch) const override;

    NewObjDynInfo GetNewObjDynInfo(uintptr_t ctor) const override;
    bool GetProfileDataForNamedAccess(PandaRuntimeInterface::MethodPtr m, uintptr_t slotId,
                                      ArenaVector<NamedAccessProfileData> *profile) override
    {
        return GetProfileDataForNamedAccessImpl(m, m, slotId, profile);
    }
    bool GetProfileDataForNamedAccess(PandaRuntimeInterface::MethodPtr m, uintptr_t funcAddress, uintptr_t slotId,
                                      ArenaVector<NamedAccessProfileData> *profile) override
    {
        return GetProfileDataForNamedAccessImpl(m, funcAddress, slotId, profile);
    }
    bool GetProfileDataForValueAccess(PandaRuntimeInterface::MethodPtr m, uintptr_t slotId,
                                      ArenaVector<NamedAccessProfileData> *profile) override
    {
        return GetProfileDataForValueAccessImpl(m, m, slotId, profile);
    }
    bool GetProfileDataForValueAccess(PandaRuntimeInterface::MethodPtr m, uintptr_t funcAddress, uintptr_t slotId,
                                      ArenaVector<NamedAccessProfileData> *profile) override
    {
        return GetProfileDataForValueAccessImpl(m, funcAddress, slotId, profile);
    }
    GlobalVarInfo GetGlobalVarInfo(MethodPtr method, uintptr_t id, uintptr_t slotId) const override
    {
        return GetGlobalVarInfoImpl(method, method, id, slotId);
    }
    GlobalVarInfo GetGlobalVarInfo(MethodPtr method, uintptr_t funcAddress, uintptr_t id,
                                   uintptr_t slotId) const override
    {
        return GetGlobalVarInfoImpl(method, funcAddress, id, slotId);
    }

    void CleanFunction(Method *method);

    void AddFunctionInMap(Method *method, ark::mem::Reference *func)
    {
        os::memory::LockHolder lock(mutex_);
        jsFunctionTable_.insert({method, func});
    }

    void CleanObjectHandles(PandaRuntimeInterface::MethodPtr method) override
    {
        CleanObjectHandles(MethodCast(method));
    }

    uint32_t GetCallableMask() const override
    {
        return HClass::GetCallableMask();
    }

    size_t GetNumMandatoryArgs() const override
    {
        return js_method_args::NUM_MANDATORY_ARGS;
    }

    void *GetConstantPool(MethodPtr method) override;
    void *GetConstantPool(uintptr_t funcAddress) override;

private:
    JSFunction *GetJSFunctionByMethod(PandaRuntimeInterface::MethodPtr m) const;

    void CleanObjectHandles(Method *method);
    void AddObjectHandle(Method *method, ObjectHeader *obj);
    uintptr_t AddFixedObjectHandle(Method *method, ObjectHeader *obj);
    bool AddProfileInfo(PandaRuntimeInterface::MethodPtr m, ArenaVector<NamedAccessProfileData> *profile,
                        ProfileTypeInfo *profileTypeInfo, uint8_t slot, uintptr_t key = 0);
    bool AddProfileValueInfo(PandaRuntimeInterface::MethodPtr m, ArenaVector<NamedAccessProfileData> *profile,
                             ProfileTypeInfo *profileTypeInfo, uint8_t slot);
    bool AddElementInfo(ArenaVector<NamedAccessProfileData> *profile, ProfileTypeInfo *profileTypeInfo, uint8_t slot);
    ProfileTypeInfo *GetProfileTypeInfo(PandaRuntimeInterface::MethodPtr m, uintptr_t slotId, uint8_t *slot) const;
    ProfileTypeInfo *GetProfileTypeInfo(uintptr_t funcAddress, uintptr_t slotId, uint8_t *slot) const;
    template <typename Func>
    bool GetProfileDataForNamedAccessImpl(PandaRuntimeInterface::MethodPtr m, Func func, uintptr_t slotId,
                                          ArenaVector<NamedAccessProfileData> *profile);
    template <typename Func>
    bool GetProfileDataForValueAccessImpl(PandaRuntimeInterface::MethodPtr m, Func func, uintptr_t slotId,
                                          ArenaVector<NamedAccessProfileData> *profile);
    template <typename Func>
    GlobalVarInfo GetGlobalVarInfoImpl(MethodPtr method, Func func, uintptr_t id, uintptr_t slotId) const;

    size_t GetLexicalEnvParentEnvIndex() const override;

    size_t GetLexicalEnvStartDataIndex() const override;

    size_t GetTaggedArrayElementSize() const override;

    JSThread *TryGetJSThread() const;
    std::pair<GlobalDictionary *, int> GetGlobalDictionaryEntry(JSThread *thread, MethodPtr method, size_t id) const;

private:
    const EcmaVM *ecmaVm_ {nullptr};
    mem::InternalAllocatorPtr internalAllocator_;
    ark::ecmascript::EcmaProfileContainer profile_;
    PandaUnorderedMap<Method *, ark::mem::Reference *> jsFunctionTable_ GUARDED_BY(mutex_);
    PandaUnorderedMap<Method *, PandaVector<ark::mem::Reference *> *> handlesByMethodTable_ GUARDED_BY(mutexH_);
    mutable os::memory::Mutex mutex_;
    mutable os::memory::Mutex mutexH_;
};

}  // namespace ark::ecmascript

#endif  // PANDA_RUNTIME_ECMASCRIPT_RUNTIME_INTERFACE_H
